pub mod errors {
    #[derive(Clone, Copy)]
    #[repr(C)]
    pub enum DatabaseError {
        /// Denotes an error when the user attempted a write operation.
        IsReadOnly = 1,
        /// Denotes an error when the user should read the C errno() for what it was.
        IOError = 2,
        /// Denotes an error when the user should run a repair tool to recover it.
        FileCorrupted = 3,
    }
}

impl From<errors::DatabaseError> for libc::c_int {
    fn from(error: errors::DatabaseError) -> libc::c_int {
        (error as i8).into()
    }
}

pub mod c_api {
    use super::errors::DatabaseError;

    /// Converts a 'DatabaseError' value to libc::c_int.
    ///
    /// # Safety
    ///
    /// This is an unsafe operation because it dereferences a passed pointer.
    #[no_mangle]
    pub unsafe extern "C" fn db_error_code(error: *const DatabaseError) -> libc::c_int {
        let db_error: &DatabaseError = unsafe {
            // SAFETY: the pointer's lifetime is greater than the current stack frame.
            &*error
        };

        let res: libc::c_int = libc::c_int::from(*db_error);

        res
    }
}

#[cfg(test)]
mod tests {
    use super::{c_api, errors::DatabaseError};

    fn assert_db_error_code(error: DatabaseError, expected_result: i32) {
        let result = unsafe { c_api::db_error_code(&error) };

        assert_eq!(expected_result, result);
    }

    #[test]
    fn db_error_as_is_read_only() {
        assert_db_error_code(DatabaseError::IsReadOnly, 1);
    }

    #[test]
    fn db_error_as_io_error() {
        assert_db_error_code(DatabaseError::IOError, 2);
    }

    #[test]
    fn db_error_as_file_corrupted() {
        assert_db_error_code(DatabaseError::FileCorrupted, 3);
    }
}
